#ifndef __POPUP_OSAL_HPP__
#define __POPUP_OSAL_HPP__

#include <stdlib.h>
#include <string>
#include "PopupLoggerUI.hpp"

#if defined _LINUX || defined _MACOS
#include <linux/types.hpp>
#elif defined _WIN32_MINGW
#include <win32-mingw/types.hpp>
#elif defined _WIN32_MSVC
#include <win32-msvc/types.hpp>
#endif

#define POPUP_MACRO_MIN(a,b) ((a) < (b) ? (a) : (b))
#define POPUP_MACRO_MAX(a,b) ((a) > (b) ? (a) : (b))

namespace PopupUtils
{
  struct Defs
  {
    static const std::string FileSeparator;
  };

  //-------------------------------------------------------
  // Mutexes
  //-------------------------------------------------------

  struct Mutex
  {
    Mutex();
    virtual ~Mutex();
    virtual int lock(bool p_try = false);
    virtual int unlock();
  protected:
    OSMutex mutex;
  };

  //-------------------------------------------------------
  // Threads
  //-------------------------------------------------------

  struct PopupThread
  {
    typedef void *(*ThreadRoutine)(void*);
    static int start(ThreadRoutine p_routine, void *p_args);
    static void sleep(int ms);
  };

  //-------------------------------------------------------
  // Sockets
  //-------------------------------------------------------

  struct Socket {

    // Windows OS doesn't support keep alive. Thus, we have to implement
    // a keep alive protocol over TCP. For this, we need to let sockets
    // be able to send a probe message that will be supported in client
    // application protocol.
    struct KeepAliveHelper {
      virtual ~KeepAliveHelper() {}
      virtual bool sendProbe() = 0;
    };

    enum Mode { BlockingSocket, NonBlockingSocket};

    static Socket *newClientSocket(const std::string  & p_host, int p_port,
        std::string *p_errMsg = 0, Socket::Mode mode = Socket::BlockingSocket);

    static Socket *newServerSocket(int p_port, int p_nbConnectionMax = 512,
        std::string *p_errMsg = 0);

    virtual ~Socket();
    Socket(const Socket &);
    Socket & operator=(Socket &);
    void close();
    bool sendBytes(const unsigned char *p_buff, size_t p_size);
    bool receiveBytes(char *p_buff, size_t p_size);

    template<class T>
    bool receiveBytes(T *p_buff, size_t p_size = 0) {
      if (p_size == 0) p_size = sizeof(T);
      return receiveBytes((char*) p_buff, p_size);
    }
    bool enableKeepAlive(bool p_doEnable = true,
                         int p_timeout = 5,
                         KeepAliveHelper *p_helper = 0);

    inline OSSocketFd fd() const { return s; }

    // Accepts a client connection (for servers only!)
    Socket* accept(std::string *p_errMsg = 0);

  private:
    OSSocketFd s;
    int *refCounter;

    Socket(OSSocketFd sockfd);

#ifdef WIN32
    // Since TCP keepalive cannot be configured per connection,
    // we have to implement our our own keepalive, at socket level
    void resetKeepalive();
    bool handleKeepalive(size_t p_sleepTimeMs);

    KeepAliveHelper *keepaliveHelper;
    bool keepaliveEnabled;
    bool keepalivePacketSent;
    int  keepaliveCountDownMs;
    int  keepaliveConfigTimeoutMs;

    static int nbSockets;
#endif
  };

  //-------------------------------------------------------
  // Signals
  //-------------------------------------------------------

  OSSigHandler signal(int p_signum, OSSigHandler p_handler);

  //-------------------------------------------------------
  // Strings
  //-------------------------------------------------------

  size_t strnlen(const char *p_size, size_t maxlen);

  //-------------------------------------------------------
  // File utilities
  //-------------------------------------------------------

  enum FileType {
    ANY,
    REGULAR_FILE,
#ifndef WIN32
    SYMLINK,
#endif
    DIRECTORY
  };

  bool fexists(const std::string & path);
  bool ftest(const std::string & p_path, enum FileType = ANY);
  size_t getFileSize(const std::string & p_path);
  bool mkdir(const std::string & path, int p_permissions = 0755);
  bool myPath(std::string & p_path);
  std::string dirname(const std::string & p_path);
  std::string basename(const std::string & p_path, bool p_stripExtension = false);
  std::string extension(const std::string & p_filename);
  std::string shiftFile(const std::string & p_filename, int p_shiftValue);
  bool dumpFileToBuffer(void *p_buffer, size_t p_bufferSize,
                        const std::string & p_filepath);
  bool dumpBufferToFile(const std::string & p_filepath,
                        const void *p_buffer, size_t p_bufferSize);
  typedef OSFileStream FileStream;
  FileStream * openFileStream(const std::string & p_path,
                          const std::string & p_mode);
  int closeFileStream(FileStream *p_stream);
  size_t fillStreamFromBuffer(FileStream *p_stream,
                              const void *p_buffer, size_t size);
  size_t fillBufferFromStream(void *p_buffer, size_t size,
                              FileStream *p_stream);
  bool getLineFromStream(char *p_buffer, size_t p_size,
      FileStream *p_stream);
  void dumpBuffer(const void *p_buffer, size_t p_size);

  unsigned long long checksum(const std::string & path);

  //-------------------------------------------------------
  // Excutable management
  //-------------------------------------------------------

  void replaceCurrentExecAndRestart(const std::string & p_newExec);
  void exec(const std::string & p_execpath);
  bool setMode(const std::string & p_filePath, int p_mode);

  //-------------------------------------------------------
  // Entropy
  //-------------------------------------------------------

  bool randomString(char *p_buffer, size_t p_size);
}

//-------------------------------------------------------
// STD library containers iteration macros
//-------------------------------------------------------

#ifndef set_foreach
#define set_foreach(__set__, __type__, __iter__) \
  std::set<__type__>::iterator __iter__;          \
  for (__iter__  = __set__.begin();               \
       __iter__ != __set__.end();                 \
       __iter__++)
#endif

#ifndef const_set_foreach
#define const_set_foreach(__set__, __type__, __iter__) \
  std::set<__type__>::const_iterator __iter__;          \
  for (__iter__  = __set__.begin();               \
       __iter__ != __set__.end();                 \
       __iter__++)
#endif

#ifndef vector_foreach
#define vector_foreach(__vector__, __type__, __iter__) \
    std::vector<__type__>::iterator __iter__;            \
    for (__iter__  = __vector__.begin();                 \
         __iter__ != __vector__.end();                   \
         __iter__++)
#endif

#ifndef const_vector_foreach
#define const_vector_foreach(__vector__, __type__, __iter__) \
    std::vector<__type__>::const_iterator __iter__;            \
    for (__iter__  = __vector__.begin();                 \
         __iter__ != __vector__.end();                   \
         __iter__++)
#endif

#ifndef map_foreach
#define map_foreach(__map__, __keytype__, __valuetype__, __iter__) \
    std::map<__keytype__, __valuetype__>::iterator __iter__;            \
    for (__iter__  = __map__.begin();                 \
         __iter__ != __map__.end();                   \
         __iter__++)
#endif

#ifndef const_map_foreach
#define const_map_foreach(__map__, __keytype__, __valuetype__, __iter__) \
    std::map<__keytype__, __valuetype__>::const_iterator __iter__;            \
    for (__iter__  = __map__.begin();                 \
         __iter__ != __map__.end();                   \
         __iter__++)
#endif

#ifndef collection_foreach
#define collection_foreach(__map__, __type__, __iter__) \
    __type__::iterator __iter__;            \
    for (__iter__  = __map__.begin();                 \
         __iter__ != __map__.end();                   \
         __iter__++)
#endif

#ifndef const_collection_foreach
#define const_collection_foreach(__map__, __type__, __iter__) \
    __type__::const_iterator __iter__;            \
    for (__iter__  = __map__.begin();                 \
         __iter__ != __map__.end();                   \
         __iter__++)
#endif

#endif // __POPUP_OSAL_HPP__
